/**
 *  功能：ESP8266 自动考勤系统
 *  作者：单片机菜鸟
 *  时间：2020-02-10
 *  描述：
 *      1.OneNet平台端：创建Http协议的产品，创建设备
 *      2.开启混杂模式，收集MAC地址
 *      2.把获取的MAC地址上传到OneNet平台
*/

#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>
#include <stdlib.h>
#include <Ticker.h>
#include "H_project.h"
#include "H_80211Frame.h"

bool isUploadMac = false;

void setup() {
  // put your setup code here, to run once:
  initSystem();
}

void loop() {
  //每1s切换一次信道 也就是每个信道的工作时间是1s
  if (millis() - hop_time >= 1000) {
    hop_time = millis();
    hop_channel++;

    if (hop_channel > 13) {
      isUploadMac = true;
      hop_channel = 1;
    }
    Serial.println(hop_channel);
    enable_promisc(hop_channel);
  }

  // 捕获完一轮之后上传一次 也就是 1-13信道
  if (isUploadMac){
    isUploadMac = false;
    if (unique_num > 0){
      upload_mac_to_server();
    } else {
      Serial.println("------------- No Match ------------------");
    }
    Serial.println("------------- END ------------------");
    Serial.println("------------- START ----------------");
  }
}

/**
 * 初始化系统
 */
void initSystem(){
    Serial.begin (115200);
    Serial.println("\r\n\r\nStart ESP8266 自动考勤");
    Serial.print("Firmware Version:");
    Serial.println(VER);
    Serial.print("SDK Version:");
    Serial.println(ESP.getSdkVersion());
    wifi_station_set_auto_connect(0);//关闭自动连接
    ESP.wdtEnable(5000);
    pinMode(LED_BUILTIN, OUTPUT);

    hop_channel = 1;
    enable_promisc(hop_channel);
    Serial.println("------------- START ----------------");
}

/**
 * 连接到AP热点
 */
void connectToAP(){
    int cnt = 0;
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
          delay(500);
          cnt++;
          Serial.print(".");
          if(cnt>=40){
            cnt = 0;
            //重启系统
            delayRestart(1);
          }
    }
}

/*
*  WiFiTick
*  检查是否需要初始化WiFi
*  检查WiFi是否连接上
*  控制指示灯
*/
void wifiTick(){
  static bool ledTurnon = false;
   if ( WiFi.status() != WL_CONNECTED ) {
       if (millis() - lastWiFiCheckTick > 1000) {
         lastWiFiCheckTick = millis();
         ledState = !ledState; digitalWrite(LED_BUILTIN, ledState);
         ledTurnon = false;
       }
    }else{
       if (ledTurnon == false) {
             ledTurnon = true;
             digitalWrite(LED_BUILTIN, 0);
        }
    }
}

/*
  判断，解析抓取到的数据包
 */
void do_process(uint8_t *buf)
{
  ieee80211_mgmt_frame *mgmt = (ieee80211_mgmt_frame *)buf;
  uint8_t type = mgmt->ctl.type;
  uint8_t sub_type = mgmt->ctl.subtype;
  uint8_t sta_addr[6];
  unsigned long now = millis();
  int do_flag = 0;
  
  if (type == 0){
    // 管理帧
   /**
    * AssociationRequest = 0, // 关联请求
    * AssociationResponse,    // 连接响应
    * ReassociationRequest,   // 重连接请求
    * ReassociationResponse,  // 重连接联响应
    * ProbeRequest,           // 探测请求
    * ProbeResponse,          // 探测响应
    * Beacon,                 // 信标，被动扫描时AP 发出，notify
    * ATIM,                   // 通知传输指示消息
    * Disassociation,         // 解除连接，notify
    * Authentication,         // 身份验证
    * Deauthentication,       // 解除认证，notify
    * Reserved                // 保留，未使用
    */
    if (sub_type == ProbeRequest){
      // 获取MAC地址
      memcpy(sta_addr, mgmt->addr2, 6);
      if (is_normal_mac(sta_addr)){
         add_mac(now,sta_addr);
      }
    } 
  } else {
     //此情况下，addr1肯定为AP,sta为addr2，手机发出
     if (mgmt->ctl.from_ds == 0 && mgmt->ctl.to_ds == 1){
        memcpy(sta_addr, mgmt->addr2, 6);
        do_flag = 1;
     }

     //此情况下，addr2肯定为AP,如果addr3等于addr2，为路由发出，
     if (mgmt->ctl.from_ds == 1 && mgmt->ctl.to_ds == 0){
       memcpy(sta_addr, mgmt->addr1, 6);
       do_flag = 1;
     }

     if (mgmt->ctl.from_ds == 0 && mgmt->ctl.to_ds == 0){
       memcpy(sta_addr, mgmt->addr2, 6);
       do_flag = 1;
     }

     if (do_flag == 0){
      return ;
     }

     if (is_normal_mac(sta_addr)){
         add_mac(now,sta_addr);
     }
  } 
}

/**
 * 函数说明：解析抓取到的数据包
 * 参数：
 *   1. buf 收到的数据包
 *   2. len buf的长度
 */ 
static void promisc_cb(uint8_t * buf, uint16_t len){

  if (len == 12 || len < 10){
      return;
  }

  struct RxPacket * pkt = (struct RxPacket*) buf;
  do_process((uint8_t *)&pkt->buf);
}


/**
 * 函数说明：启用特定频道的混杂模式
 * 参数：
 *   1. channel 设置频道
 */
static void enable_promisc(int channel){
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  wifi_set_channel(channel);  // 初始化为通道
  wifi_promiscuous_enable(0); // 先关闭混杂模式
  // 注册混杂模式下的接收数据的回调函数，每收到一包数据，都会进入注册的回调函数里面。
  wifi_set_promiscuous_rx_cb(promisc_cb);
  wifi_promiscuous_enable(1); // 开启混杂模式
}

/**
 * 函数说明：关闭混杂模式
 * 参数：
 *   1. channel 设置频道
 */
static void disable_promisc(int channel){
  wifi_promiscuous_enable(0);
}

/*
 *格式化打印mac
 */
static void print_mac(const uint8_t * mac){
  char text[32];
  sprintf(text, "%02X:%02X:%02X:%02X:%02X:%02X",
          mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  Serial.println(text);
}

/*
  *判断是否是普通mac
 */
static bool is_normal_mac(const uint8_t * mac)
{
  char text[32];
  char c;
  sprintf(text, "%02X:%02X:%02X:%02X:%02X:%02X",
          mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  c = text[1];
  if (c == '0' || c == '4' || c == '8' || c == 'C') {
    return true;
  }

  return  false;
}

/*
  * 保存mac地址到已经上传列表
 */
static void add_upload_mac(const uint8_t *mac){
  for (int i = 0; i < unique_upload_num; i++) {
    if (memcmp(mac, upload_mac[i].mac, 6) == 0) {
      // 已经上传过
      return;
    }
  }

  if (unique_upload_num < UPLOAD_MAC){
     memcpy(&upload_mac[unique_upload_num].mac, mac, 6);
     unique_upload_num++;
  } else {
     Serial.println("unique_upload_num OVER MAX==========");
  }
}

/*
  *检测扫描到的mac是否已存在，
  *不存在，并且还有空间，添加保存 返回true
  *没空间返回false
  *已存在更新时间戳，返回false
 */
static bool add_mac(unsigned long now, const uint8_t *mac){
  int i;
  for (i = 0; i < unique_upload_num; i++) {
    if (memcmp(mac, upload_mac[i].mac, 6) == 0) {
      // 已经上传过
      return false;
    }
  }
  
  // 判断是否已经存在过
  for (i = 0; i < unique_num; i++) {
    if (memcmp(mac, unique_mac[i].mac, 6) == 0) {
      // 更新时间
      unique_mac[i].last_seen = now;
      return false;
    }
  }

  // 还有足够空间就添加进去
  if (unique_num < MAX_MAC) {
    Serial.print("New Mac: ");
    print_mac(mac);
    memcpy(&unique_mac[unique_num].mac, mac, 6);
    unique_mac[unique_num].last_seen = now;
    unique_num++;
    return true;
  } else {
    Serial.println("unique_num OVER MAX==========");
    // could not fit it
    return false;
  }
}

/*
 * 功能：mac生命周期检测，从列表清除长时间未检测到的mac
 * 参数：
 *   1. now 当前时间
 *   2. expire 过期间隔
 */
static void expire_mac(unsigned long now, unsigned long expire) {
  char text[32];
  int i;
  for (i = 0; i < unique_num; i++) {
    if ((now - unique_mac[i].last_seen) > expire) {
      // 过期之后 用数组的最后一个内容覆盖过期位置
      if (--unique_num > i) {
        sprintf(text, "%10d: ", now);
        Serial.print(text);
        print_mac(unique_mac[i].mac);
        sprintf(text, " expired: %d\n", unique_num);
        Serial.print(text);
        memcpy(&unique_mac[i], &unique_mac[unique_num], sizeof(mac_t));
      }
    }
  }
}

/*
 * 上传Mac地址
 */
static bool upload_mac_to_server(){
  uint8_t *mac;
  
  disable_promisc(hop_channel);

  DynamicJsonBuffer jsonBuffer;
  //创建根，也就是顶节点
  JsonObject& root = jsonBuffer.createObject();
  //在root对象中加入data数组
  JsonArray& datastreams = root.createNestedArray("datastreams");
  JsonObject& stream = datastreams.createNestedObject();
  stream["id"] = "Mac";
  JsonArray& datapoints = stream.createNestedArray("datapoints");
  for(int i = 0;i < unique_num ; i ++){
      char text[32];
      JsonObject& value1 = datapoints.createNestedObject();
      mac = unique_mac[i].mac;
      sprintf(text, "%02X:%02X:%02X:%02X:%02X:%02X",
          mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
      value1["value"] = text;
  }
  
  String data;
  root.printTo(data);
  String devData;
  root.prettyPrintTo(devData);
  Serial.println(devData);  

  connectToAP();
  retry = 0;
  Serial.println("Upload Start");
  while(!postToDeviceDataPoint(data)){
    retry ++;
    ESP.wdtFeed();
    if(retry == 20){
       retry = 0;
       delayRestart(1);
    }
  }
  Serial.println("Upload Success!"); 
  
  for (int i = 0; i < unique_num; i++){
    add_upload_mac(unique_mac[i].mac);      
    memset(&unique_mac[i], 0x00, sizeof(mac_t));
  }
  unique_num = 0;
  enable_promisc(hop_channel);
  return true;
}
